import { hasOwnProperty } from '@/utils/common'
import type { DBInstance, LimitInfo, VerifyFailureRecordItem } from './db'
import { formatDuration } from '@/utils/format'

export class Limiter {
  private readonly db: DBInstance
  private EXPIRATION_TIME: number // 记录过期时间 (毫秒)

  constructor(db: DBInstance, expirationTime = 30 * 60 * 1000) {
    this.db = db
    // 默认30分钟
    this.EXPIRATION_TIME = expirationTime
    this.startCleanupTask()
  }

  /**
   * 添加校验失败记录
   */
  addVerifyFailureRecord(ip: string) {
    this.db.update(({ verifyFailureRecord }) => {
      const currentTime = Date.now()

      const record = this.getVerifyFailureRecord(ip)

      if (record) {
        if (currentTime - record.timestamp < this.EXPIRATION_TIME) {
          record.count += 1
        } else {
          // 如果记录已经过期，则重置计数
          record.count = 1
        }
        record.timestamp = currentTime
      } else {
        verifyFailureRecord[ip] = { count: 1, timestamp: currentTime }
      }

      this.determineLimit(ip)
    })
  }

  /**
   * 删除某个IP的校验失败记录
   */
  removeVerifyFailureRecord(ip: string) {
    this.db.update(({ verifyFailureRecord }) => {
      if (hasOwnProperty(verifyFailureRecord, ip)) {
        delete verifyFailureRecord[ip]
      }
    })
  }

  /**
   * 获取某个IP的校验失败记录
   */
  getVerifyFailureRecord(ip: string): VerifyFailureRecordItem | null {
    return this.db.data.verifyFailureRecord[ip] || null
  }

  /**
   * 添加限制信息
   */
  addLimitInfo(info: LimitInfo) {
    this.db.update(({ limitInfo }) => {
      const index = limitInfo.findIndex((v) => v.ip === info.ip)
      if (index === -1) {
        limitInfo.push(info)
      } else {
        // 如果已经存在，则覆盖
        limitInfo[index] = info
      }
    })
  }

  /**
   * 获取限制信息
   */
  getLimitInfo(ip: string): LimitInfo | null {
    return this.db.data.limitInfo.find((v) => v.ip === ip) || null
  }

  /**
   * 移除限制信息
   */
  removeLimitInfo(ip: string) {
    this.db.update(({ limitInfo }) => {
      const index = limitInfo.findIndex((v) => v.ip === ip)
      if (index !== -1) {
        limitInfo.splice(index, 1)
      }
    })
  }

  /**
   * 获取限制结果
   */
  getLimitResult(ip: string): LimitResult {
    const info = this.getLimitInfo(ip)

    const res: LimitResult = { isLimited: false }

    if (!info) return res
    // 如果time为0，则表示永久限制
    if (info.time === 0) {
      res.isLimited = true
      res.info = info
      return res
    }

    const now = Date.now()
    const startTime = info.startTime || now

    if (startTime + info.time + this.EXPIRATION_TIME < now) {
      // 如果限制时间已经过去，则移除限制信息
      this.removeLimitInfo(ip)
      return res
    }

    // 在限制范围内
    if (now > startTime && now < startTime + info.time) {
      res.isLimited = true
      res.info = info
      res.tip = 'Please try again in ' + formatDuration(info.time - (now - startTime))
    }

    return res
  }

  // 判定ip要不要被限制，如果要，则添加限制信息
  private determineLimit(ip: string) {
    const record = this.getVerifyFailureRecord(ip)

    if (!record) return

    const info = this.getLimitInfo(ip)

    if (record.count >= 9) {
      const time = 0
      if (!info || info.time !== time) {
        this.addLimitInfo({
          ip,
          time
        })
        this.removeVerifyFailureRecord(ip)
      }
    } else if (record.count >= 6) {
      const time = 30 * 60 * 1000
      if (!info || info.time !== time) {
        this.addLimitInfo({
          ip,
          time,
          startTime: Date.now()
        })
      }
    } else if (record.count >= 3) {
      const time = 2 * 60 * 1000
      if (!info || info.time !== time) {
        this.addLimitInfo({
          ip,
          time,
          startTime: Date.now()
        })
      }
    }
  }

  // 定期清理过期的记录
  private startCleanupTask() {
    setInterval(() => {
      const currentTime = Date.now()
      let hasChanged = false

      for (const ip in this.db.data.verifyFailureRecord) {
        const record = this.db.data.verifyFailureRecord[ip]
        if (currentTime - record.timestamp >= this.EXPIRATION_TIME) {
          delete this.db.data.verifyFailureRecord[ip]
          hasChanged = true
        }
      }

      if (hasChanged) {
        this.db.write()
      }
    }, this.EXPIRATION_TIME)
  }
}

export type LimitResult = { isLimited: boolean; info?: LimitInfo; tip?: string }
